#pragma once

#include "Connection.h"
#include "NetworkBase.h"
#include "LinkedEventPair.h"
#include "PNGDescriptor.h"
#include "CombinationGenerator.h"

using namespace System;

namespace SpikingNeuronLib
{
	// called when a polychronous group has been found (via FoundPNG event below)
	public delegate void PNGFinderFoundPNGEventHandler(
		String^ tag, int numPolychronousGroups, int anchorNeuron, int numFired, int maxLinkLevel, int lastFired,
		array<FiringEvent>^ groupEvents, PNGDescriptor^ linkedEvents
		);

	public enum class PNGSearchAlgorithm : int
	{
		Estimation,     // estimates convergence of inputs
		Simulation      // uses simulation to more closely estimate input convergence
	};

	public value struct VirtualEvent
	{
	public:
		property int PreNeuron;
		property int PostNeuron;
		property int Delay;
		property double Weight;
		property int LinkLevel;

		VirtualEvent(int preNeuron, int postNeuron, int delay, double weight, int linkLevel)
		{
			PreNeuron = preNeuron;
			PostNeuron = postNeuron;
			Delay = delay;
			Weight = weight;
			LinkLevel = linkLevel;
		}
	};

	// The functions in this class are derived from the work of Eugene Izhikevich (see http://www.izhikevich.org/publications/spnet.htm)
	// Both his original event-triggered (fast) algorithm and the newer activity-triggered (slow, but more precise) algorithm are implemented
	// although some of the implementation details have been substantially changed to enable interfacing with multiple network types
	public ref class PNGFinder abstract
	{
	protected:
		static int _maxPNGLength = 1000;

		property int GroupSize;
		property array<int>^ NumPostspikes;
		property array<FiringEvent>^ GroupEvents;
		property List<LinkedEventPair^>^ Links;

		property Dictionary<int, List<Connection^>^>^ IncomingConnectionDictionary;
		property Dictionary<Tuple<int, int>^, Connection^>^ ConnectionPairDictionary;
		property Dictionary<int, Dictionary<int, List<Connection^>^>^>^ GroupedConnectionDictionary;

		PNGFinder() {}

		int FindPolychronousGroups(String^ tag, int anchorNeuron, int maxDelay, double weightLimit, int numExcitatoryNeurons, int totalNeurons, int verboseLevel);

		virtual int SearchCurrentTriplet(String^ tag, int anchorNeuron, array<int>^ selectedNeurons, int maxDelay, int numPolychronousGroups,
			int groupSize, double weightLimit, int maxPNGLength, int numExcitatoryNeurons, int totalNeurons,
			Dictionary<Tuple<int, int>^, Connection^>^ connectionPairDictionary,
			Dictionary<int, Dictionary<int, List<Connection^>^>^>^ groupedConnectionDictionary, int verboseLevel) abstract;

		void OnFoundPNG(String^ tag, int numPolychronousGroups, int anchorNeuron, int numFired, int maxLinkLevel, int lastFired,
			array<FiringEvent>^ groupEvents, List<LinkedEventPair^>^ linkedEvents);

		// Get all incoming connections for each postsynaptic neuron
		Dictionary<int, List<Connection^>^>^ GetIncomingConnections(List<Connection^>^ connections);
		// Get all outgoing connections for each presynaptic neuron
		Dictionary<int, List<Connection^>^>^ GetOutgoingConnections(List<Connection^>^ connections);
		Dictionary<int, List<Connection^>^>^ GetConnectionsByNeuron(List<Connection^>^ connections, Func<Connection^, int>^ selectNeuron);
		Dictionary<Tuple<int, int>^, Connection^>^ CreateConnectionDictionary(List<Connection^>^ connections);

		// create a dictionary, keyed by presynaptic neuron that maps each key onto a set of outgoing connections grouped by the connection delay
		Dictionary<int, Dictionary<int, List<Connection^>^>^>^ GenerateDelayGroupedConnectionsByPreNeuron(Dictionary<int, List<Connection^>^>^ outgoingConnectionDictionary);

	public:
		PNGFinder(List<Connection^>^ connections, int groupSize);
		~PNGFinder() {}

		event PNGFinderFoundPNGEventHandler^ FoundPNG;

		void BindEvent(PNGFinderFoundPNGEventHandler^ handler);
		void UnbindEvent(PNGFinderFoundPNGEventHandler^ handler);

		int FindPolychronousGroups(NetworkBase^ network, int fromNeuron, int toNeuron, double weightLimit, int verboseLevel);
		int FindPolychronousGroups(NetworkBase^ network, int fromNeuron, int toNeuron, double weightLimit, int verboseLevel, String^ tag);

		// Search a specific triplet
		static int SearchTriplet(String^ tag, PNGSearchAlgorithm algorithm, NetworkBase^ network, int anchorNeuron, array<int>^ selectedNeurons, double weightLimit, int groupSize, PNGFinderFoundPNGEventHandler^ handler, int verboseLevel);

		static PNGFinder^ Create(PNGSearchAlgorithm algorithm, NetworkBase^ network, int groupSize);
		static PNGFinder^ Create(PNGSearchAlgorithm algorithm, NetworkBase^ network, int groupSize, Func<NetworkBase^, List<Connection^>^>^ connectionSelector);

		static int FindPolychronousGroups(PNGSearchAlgorithm algorithm, NetworkBase^ network, int fromNeuron, int toNeuron, double weightLimit, int groupSize, int verboseLevel);
		static int FindPolychronousGroups(PNGSearchAlgorithm algorithm, NetworkBase^ network, int fromNeuron, int toNeuron, double weightLimit, int groupSize, int verboseLevel, String^ tag);
	};
}

